#!/usr/bin/env ruby -Ku
require 'rubygems'
require 'yaml'
require 'cgi'
require "logger"
require "flvtool2/base"
require "open-uri"
require "rexml/document"
require "optparse"
require "kconv"
require "nicoagent"
require "time"
require "uri"

class VideoConverter
  def self.convert(in_flv, out_dir, soundonly, config)
    size, aspect, info = get_aspect_ratio(in_flv)
    out_mp4 = "#{out_dir}#{File::SEPARATOR}#{File.basename(in_flv, '.*')}.mp4"
    param = eval(config['ffmpeg'][soundonly ? 'sound_param' : 'video_param'])
    command = "#{config['ffmpeg']['path']} -i \"#{in_flv}\" #{param} \"#{out_mp4}\""
    $logger.debug(command)
    system(command)
    $logger.debug("transcode finished. status: #{$?}")
    to_delete = if $? == 0 then in_flv else out_mp4 end
    File.delete(to_delete) if File.exist?(to_delete)
    raise "transcode failed." unless File.exist?(out_mp4)
  end

  private
  def self.get_aspect_ratio(in_flv)
    flv_info = {
      "audiodatarate" => 128,
      "audiodelay" => 0,
      "videodatarate" => 600
    }
    size = "480x272"
    aspect = "16:9"
    begin
      open(in_flv, File::RDONLY | File::BINARY) do |flv|
        stream = FLV::FLVStream.new(flv, nil, nil)
        info = stream.on_meta_data_tag
        ratio = info['width'].to_f / info['height'].to_f
        if ratio < 1.5
          size = "432x320"
          aspect = "4:3"
        end
        flv_info.each do |k, v| 
          nv = info[k];
          flv_info[k] = info[k] if nv && nv > v
        end
      end
    rescue
      $logger.warn("failed to get flv_info from #{in_flv}.")
      $logger.warn("#{$!.to_s.toutf8} #{$!.backtrace}")
    end
    return size, aspect, flv_info
  end
end

class FileProcessor
  def self.process(listoption, config)
    $logger.info("target: #{listoption['podname']}")
    Dir::chdir(listoption['flvpath'])
    Dir::glob(["*.flv","*.avi"]) do |flv|
      VideoConverter.convert(
        "#{listoption['flvpath']}#{File::SEPARATOR}#{flv}", 
        listoption['mp4path'], listoption['soundonly'], config)
    end
    items = Dir::glob("*.mp4").map do |mp4|
      {:title => File.basename(mp4, '.mp4'), 
       :url => "#{listoption['mp4url']}#{mp4}", 
       :date => File.ctime(mp4), 
       :content => ''}
    end
    ListProcessor.write_rss(listoption['podname'], items, listoption)
  end
end
class ListProcessor
  def self.process(nico_agent, listoption, config)
    hist_file = "#{listoption['mp4path']}#{File::SEPARATOR}history.txt"
    old_hist = read_history(hist_file)
    list_name, items, new_hist = read_list(nico_agent, listoption, config, old_hist)
    write_rss(list_name, items, listoption)
    write_history(hist_file, new_hist)
    cleanup_garbage("#{listoption['mp4path']}", new_hist) if listoption['cleanup']
  end

  def self.read_history(path)
    ret = {}
    if File.exist?(path)
      ret = YAML.load_file(path)
      ret = {} unless ret.is_a?(Hash)
    end
    ret
  end
  
  def self.read_list(nico_agent, listoption, config, old_hist)
    video_id_list = []
    list_name = nil
    #get video_id list
    if !listoption['listurl'].nil?
      #get videoid list from published list
      mylist = REXML::Document.new URI.parse(listoption['listurl']).read
      $logger.info("target:#{listoption['listurl']}")
      mylist.root.each_element("item") do |item|
        link = item.elements["link"]
        if link.text =~ /(sm[0-9]+)\Z/
          video_id_list << $1
        end
      end
      list_name = mylist.root.elements["channel/title"].text
      if list_name =~ /マイリスト\s*(.+)\Z/
        list_name = $1
      end
    elsif !listoption['tagname'].nil?
      #get videoid list from tag search
      list_name = listoption['tagname'].toutf8
      $logger.info("target:#{list_name}")
      video_id_list = nico_agent.search_by_tag(list_name, :POSTED_NEW).map do |entry| entry[:video_id] end
    elsif !listoption['keyword'].nil?
      #get videoid list from tag search
      list_name = listoption['keyword'].toutf8
      $logger.info("target:#{list_name}")
      video_id_list = nico_agent.search_by_keyword(list_name, :POSTED_NEW).map do |entry| entry[:video_id] end
    end
    
    items = []
    new_hist = {}
    video_id_list.each do |video_id|
      begin
          next if video_id[0..1] == 'nm'
          mp4name = "#{listoption['mp4path']}#{File::SEPARATOR}#{video_id}.mp4"
          info = old_hist[video_id]
          if info.nil?
            if File.exist?(mp4name) 
              info = nico_agent.get_flv(video_id, nil)
              sleep 10 #wait 10 seconds.
            else 
              info = nico_agent.get_flv(video_id, listoption['flvpath'])
              VideoConverter.convert(
                "#{listoption['flvpath']}#{File::SEPARATOR}#{video_id}.flv", 
                listoption['mp4path'], listoption['soundonly'], config)
            end
            new_hist[video_id] = info
          else 
            new_hist[video_id] = info
            $logger.info("#{info[:title]}")
            if File.exist?(mp4name)
              $logger.debug("#{mp4name} already exists.")
            else 
              $logger.debug("#{video_id} exists in history.")
              # Once mp4file is deleted, exclude it from the published list.
              next
            end
          end
          info[:url] = "#{listoption['mp4url']}#{video_id}.mp4"
          items << info
      rescue
        $logger.warn("#{$!.to_s.toutf8} #{$!.backtrace}")
      end
    end
    
    return list_name, items, new_hist
  end

  def self.write_rss(list_name, items, listoption)
    open("#{listoption['xmlfilename']}", File::WRONLY | File::CREAT | File::TRUNC) do |rss|
      #HEAD
      rss.write <<"HEAD"
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:itunes="http://www.itunes.com/DTDs/Podcast-1.0.dtd" version="2.0">
<channel>
<title>#{CGI.escapeHTML(list_name)}</title>
HEAD

      #ITEMS
      items.each do |item|
        rss.write <<"ITEM"
<item>
<title>#{CGI.escapeHTML(item[:title])}</title>
<pubDate>#{item[:date].xmlschema}</pubDate>
<enclosure url='#{URI.escape(item[:url])}' length=""  type='video/mpeg' />
<itunes:subtitle>#{CGI.escapeHTML(item[:content])}</itunes:subtitle>
</item>
ITEM
      end

      #FOOT
      rss.write <<FOOT
</channel>
</rss>
FOOT
    end
  end
  
  def self.write_history(file, history)
    File.open(file, File::WRONLY | File::CREAT | File::TRUNC) do |hist|
      hist.write(history.to_yaml)
    end
  end
  
  def self.cleanup_garbage(dir, new_hist)
    Dir.foreach(dir) do |file|
      next unless file =~ /\.mp4\Z/
      video_id = File.basename(file, '.mp4')
      unless new_hist.include?(video_id)
        begin 
          $logger.debug("#{file} doesn't exist in this list. deleted.")
          File.delete("#{dir}#{File::SEPARATOR}#{file}")
        rescue 
          $logger.warn("#{$!.to_s.toutf8} #{$!.backtrace}")
        end
      end
    end
  end
end

yamlfile = "nico.yaml"
ARGV.options {|opt|
  opt.on('-c yaml') do |v| yamlfile = v end
  opt.parse!
}
unless File.exist? yamlfile
  STDERR.print "ruby nico4touch.rb -c nico.yaml"
  exit
end

yaml = YAML.load_file(yamlfile)
config = yaml['config']
mylists = yaml['mylist']
$logger = Logger.new(config["logfile"] || "nico.log")
$logger.info("START")
nico = NicoAgent.new(config["mail"], config["password"])
nico.logger = $logger
mylists.each do |listoption| 
  ['mp4path', 'flvpath'].each do |s|
    path = listoption[s]
    Dir.mkdir(path) unless File.exist?(path)
  end
  begin
    if listoption['podname']
      FileProcessor.process(listoption, config)
    else 
      ListProcessor.process(nico, listoption, config)
    end
  rescue
    $logger.warn("#{$!.to_s.toutf8} #{$!.backtrace}")
  end
end
$logger.info("END")
